#include "QQNetWork.h"

/*
QAtomicInteger<int> mTcpConnectState = QTcpSocket::UnconnectedState;

QQNetWorkReadThread::QQNetWorkReadThread(QObject* parent)
	: QThread(parent)
{
	mTcpSocket = nullptr;
}

QQNetWorkReadThread::~QQNetWorkReadThread()
{
	if (this->isRunning())
		this->wait();
}

void QQNetWorkReadThread::setTcpSocket(QTcpSocket* tcpSocket)
{
	mTcpSocket = tcpSocket;
}

void QQNetWorkReadThread::subscribeNetwork(const QObject* receiver, const QString& key, const QString& slot)
{
	QMutexLocker locker(&mMutex);
	mReceivers.insert(key, const_cast<QObject*>(receiver));
	mSlots.insert(key, slot);
}

void QQNetWorkReadThread::unsubscribeNetwork(const QString& key)
{
	QMutexLocker locker(&mMutex);
	mReceivers.remove(key);
	mSlots.remove(key);
}

void QQNetWorkReadThread::run()
{
	while (mTcpConnectState == QTcpSocket::ConnectedState)
	{
		if (mTcpSocket->bytesAvailable() <= PACKAGE_SIZE_INFO)
		{
			this->msleep(100);
			continue;
		}
		if (mTcpConnectState == QTcpSocket::ConnectedState)//maybe trigger when tcpsocket is disconnected,this if is necessary
		{
			buffer.append(mTcpSocket->readAll());
			while (buffer.size() > PACKAGE_SIZE_INFO)
			{
				QByteArray packageSizeInfo = buffer.left(PACKAGE_SIZE_INFO);
				int size = packageSizeInfo.toInt();
				if (size > buffer.size())
					break;
				QByteArray data = buffer.left(size);
				buffer.remove(0, PACKAGE_SIZE_INFO + size);
				notify(data);
			}
		}
	}
}

void QQNetWorkReadThread::notify(const QByteArray& data)
{
	QMutexLocker locker(&mMutex);
	QJsonObject json = QJsonDocument::fromJson(data).object();
	QString model = json.value("model").toString();
	QJsonObject value = json.value("value").toObject();
	QMetaObject::invokeMethod(mReceivers.value(model), mSlots.value(model).toStdString().c_str(), Qt::QueuedConnection, Q_ARG(QJsonObject, value));
}


QQNetWorkWriteThread::QQNetWorkWriteThread(QObject* parent)
	: QThread(parent)
{
	mTcpSocket = new QTcpSocket(this);
	mTcpSocket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
	mTcpSocket->connectToHost(IP, PORT);
	mTcpSocket->waitForConnected();
	mTcpSocket->moveToThread(this);
	mWrites = new boost::lockfree::spsc_queue<QByteArray>(100);
}

QQNetWorkWriteThread::~QQNetWorkWriteThread()
{
	if (this->isRunning())
		this->wait();
}

void QQNetWorkWriteThread::submit(const QByteArray& work)
{
	mWrites->push(work);
	if (!this->isRunning())
		this->start();
}

void QQNetWorkWriteThread::run()
{
	while (mTcpConnectState == QTcpSocket::ConnectedState)
	{
		while (!mWrites->empty())
		{
			auto data = mWrites->front();
			if (mTcpConnectState == QTcpSocket::ConnectedState)//maybe trigger when tcpsocket is disconnected,this if is necessary
			{
				mTcpSocket->write(data);
				mTcpSocket->flush();
				mWrites->pop();
			}
		}
		this->msleep(100);
	}
}
*/

QQNetWork::QQNetWork(QObject* parent)
	: QObject(parent)
{
	mTimer = new QTimer(this);
	mIsConnected = false;
	mTcpSocket = new QTcpSocket(this);
	mTcpSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
	mTcpSocket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
	connect(mTcpSocket, &QTcpSocket::readyRead, this, &QQNetWork::doReadyRead);
	connect(mTcpSocket, &QTcpSocket::errorOccurred, this, &QQNetWork::doTcpError);
}

QQNetWork::~QQNetWork()
{
}

QQNetWork* QQNetWork::getInstance()
{
	static QQNetWork* instance = nullptr;
	if (instance == nullptr)
		instance = new QQNetWork(qApp);
	return instance;
}

void QQNetWork::init()
{
	mTcpSocket->connectToHost(IP, PORT);
}

void QQNetWork::subscribeNetwork(const QObject* receiver, const QString& key, const QString& slot)
{
	mReceivers.insert(key, const_cast<QObject*>(receiver));
	mSlots.insert(key, slot);
}

void QQNetWork::unsubscribeNetwork(const QString& key)
{
	mReceivers.remove(key);
	mSlots.remove(key);
}

void QQNetWork::entry(const QString& account, const QString& password)
{
}

void QQNetWork::registerAccount(const QString& nickname, const QString& phone, const QString& password)
{
}

void QQNetWork::findPassword(const QString& account, const QString& phone)
{
}

void QQNetWork::sendMessage(const QString& sender, const QString& receiver, const QString& message)
{
}

void QQNetWork::sendFile(const QString& sender, const QString& receiver, const QString& fileName)
{
}

void QQNetWork::sendFile(const QString& sender, const QString& receiver, const QString& key, int index, int totalPieceCount, const QByteArray& data)
{
}

void QQNetWork::sendAudioRequest(const QString& sender, const QString& receiver)
{
}

void QQNetWork::cancelAudioRequest(const QString& sender, const QString& receiver)
{
}

void QQNetWork::sendAudioResponse(const QString& sender, const QString& receiver, bool accept)
{
}

void QQNetWork::sendVideoRequest(const QString& sender, const QString& receiver)
{
}

void QQNetWork::cancelVideoRequest(const QString& sender, const QString& receiver)
{
}

void QQNetWork::sendVideoResponse(const QString& sender, const QString& receiver, bool accept)
{
}

void QQNetWork::addFriend(const QString& account, const QString& friendAccount)
{
}

void QQNetWork::deleteFriend(const QString& account, const QString& friendAccount)
{
}

void QQNetWork::queryFriend(const QString& account)
{
}

void QQNetWork::agreeFriendRequest(const QString& account, const QString& friendAccount)
{
}

void QQNetWork::rejectFriendRequest(const QString& account, const QString& friendAccount)
{
}

void QQNetWork::doReadyRead()
{
	static QByteArray buffer;
	buffer.append(mTcpSocket->readAll());
	while (buffer.size() > PACKAGE_SIZE_INFO)
	{
		QByteArray packageSizeInfo = buffer.left(PACKAGE_SIZE_INFO);
		int size = packageSizeInfo.toInt();
		if (size > buffer.size())
			break;
		QByteArray data = buffer.left(size);
		buffer.remove(0, PACKAGE_SIZE_INFO + size);
		notify(data);
	}
}

void QQNetWork::doHandleData(const QJsonObject& data)
{

}

void QQNetWork::doTcpConnected()
{
	mIsConnected = true;
	emit connectStateChanged(mIsConnected);
	mTimer->stop();
	disconnect(mTimer, &QTimer::timeout, this, &QQNetWork::doTimeOut);
}

void QQNetWork::doTcpDisconnected()
{
	mIsConnected = false;
	emit connectStateChanged(mIsConnected);
	connect(mTimer, &QTimer::timeout, this, &QQNetWork::doTimeOut);
	mTimer->start(1000);
}

void QQNetWork::doTimeOut()
{
	if (mMaxRetryCount-- == 0)
	{
		emit errorOccurred(QAbstractSocket::SocketError::ConnectionRefusedError);
		qDebug() << "connect failed and retry count is 0";
	}
	else if (mTcpSocket->state() != QAbstractSocket::ConnectedState)
	{
		mTcpSocket->connectToHost(IP, PORT);
		qDebug() << "retry connect to host";
	}
	mTimer->stop();
}

void QQNetWork::doTcpError(QAbstractSocket::SocketError error)
{
	switch (error)
	{
	case QAbstractSocket::ConnectionRefusedError:
	{
		mTcpSocket->abort();
		mTcpSocket->connectToHost(IP, PORT);
		break;
	}
	case QAbstractSocket::SocketTimeoutError:
	{
		mTcpSocket->abort();
		mTcpSocket->connectToHost(IP, PORT);
		break;
	}
	default:
		break;
	}
}

void QQNetWork::package(const QByteArray& data)
{
	QQNetWorkPackage netWorkPackage;
	QString keyStr = qq_settings->getUserInfo()->account + QString::number(QDateTime::currentMSecsSinceEpoch());
	QByteArray key = QCryptographicHash::hash(keyStr.toUtf8(), QCryptographicHash::Sha256);
	netWorkPackage.key = QUuid::createUuid().toString();
	netWorkPackage.data = data;
	netWorkPackage.retryCount = 0;
	mUnsendedPackages.enqueue(netWorkPackage);
}

void QQNetWork::send(const QQNetWorkPackage& netWorkPackage)
{
	mTcpSocket->write(netWorkPackage.data);
}

void QQNetWork::notify(const QByteArray& data)
{
	QJsonObject json = QJsonDocument::fromJson(data).object();
	QString model = json.value("model").toString();
	QString type = json.value("type").toString();
	QJsonObject value = json.value("value").toObject();
	if (type == "ack")
		doHandleData(value);
	QMetaObject::invokeMethod(mReceivers.value(model), mSlots.value(model).toStdString().c_str(), Qt::QueuedConnection, Q_ARG(QJsonObject, value));
}

void QQNetWork::updateToken(const QString& token)
{

}

void QQNetWork::updateQueue(const QString& key)
{
	mSendedPackages.remove(key);
}
